package org.destinyshine.cricket.container;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.*;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionListener;
import javax.xml.bind.JAXBException;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.destinyshine.cricket.connector.Connection;
import org.destinyshine.cricket.connector.ConnectionReceiver;
import org.destinyshine.cricket.connector.HttpServletResponseImpl;
import org.destinyshine.cricket.context.WebApplicationContextFacade;
import org.destinyshine.cricket.context.WebAppClassLoader;
import org.destinyshine.cricket.context.description.FilterMapping;
import org.destinyshine.cricket.context.description.ServletMapping;
import org.destinyshine.cricket.deploy.WebAppDeployHelper;
import org.destinyshine.cricket.jaxbmodel.ContextParamElement;
import org.destinyshine.cricket.server.AbstractLifecycle;
import org.destinyshine.cricket.server.LifecycleException;
import org.destinyshine.cricket.server.LifecycleState;
import org.destinyshine.cricket.utils.MultiMap;

/**
 * Created by destinyliu on 2016/2/28.
 */
public class WebApplicationContext extends AbstractContainerComponent implements ConnectionReceiver {

    private static final Logger logger = LogManager.getFormatterLogger();

    private WebApplicationContainer container;

    private final String contextPath;
    private final File contextBase;

    private List<ServletContextListener> servletContextListeners = new ArrayList<>();
    // TODO::implemention
    private List<ServletContextAttributeListener> servletContextAttributeListeners = new ArrayList<>();

    // TODO::implemention
    private List<HttpSessionListener> httpSessionListeners = new ArrayList<>();
    // TODO::implemention
    private List<HttpSessionAttributeListener> httpSessionAttributeListeners = new ArrayList<>();

    private List<HttpSessionActivationListener> httpSessionActivationListeners = new ArrayList<>();

    /**
     * httpSessionAttribute Object
     */
    private List<HttpSessionBindingListener> httpSessionBindingListeners = new ArrayList<>();

    // TODO::implemention
    private List<ServletRequestListener> servletRequestListeners = new ArrayList<>();
    // TODO::implemention
    private List<ServletRequestAttributeListener> servletRequestAttributeListeners = new ArrayList<>();

    private List<ServletWrapper> servletWrappers = new ArrayList<>();
    private List<FilterWrapper> filterWrappers = new ArrayList<>();

    // TODO::implemention
    private List<FilterMapping> filterMappings = new ArrayList<>();

    private List<ServletMapping> servletMappings = new ArrayList<>();

    private ServletContext servletContext;

    private WebAppClassLoader webAppClassLoader;

    private WebAppDeployHelper webAppDeployHelper;

    private WebApplicationContextFacade webApplicationContextFacade;

    private String displayName;

    private Map<String, String> contextParams = new HashMap<>();

    public WebApplicationContext(WebApplicationContainer container, String contextPath, String contextBase) throws FileNotFoundException, JAXBException {
        this.container = container;
        this.contextPath = contextPath;
        this.contextBase = new File(contextBase);
        this.webAppDeployHelper = new WebAppDeployHelper(this);
    }


    public void addClassPathEntry(final URL url) {
        webAppClassLoader.addURL(url);
    }

    public void addClassPathEntry(final File file) {
        try {
            webAppClassLoader.addURL(file.toURI().toURL());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    public void addClassPathEntrysDir(final File dir) {
        File libDir = dir;
        String[] libDirChildren = libDir.list();
        if (ArrayUtils.isNotEmpty(libDirChildren)) {
            for (String file : libDirChildren) {
                if (file.endsWith(".jar")) {
                    try {
                        webAppClassLoader.addURL(new URL("file:///" + dir.getAbsolutePath() + "/WEB-INF/lib/" + file));
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public Object createListener(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        Class<?> listenerClass = Class.forName(className, true, webAppClassLoader);
        Object listener = listenerClass.newInstance();
        return listener;
    }

    public void addListener(String listenerClass) {

        Object listener = null;
        try {
            listener = createListener(listenerClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        if (listener instanceof ServletContextListener) {
            servletContextListeners.add((ServletContextListener) listener);
        }
        if (listener instanceof ServletContextAttributeListener) {
            servletContextAttributeListeners.add((ServletContextAttributeListener) listener);
        }
        if (listener instanceof HttpSessionListener) {
            httpSessionListeners.add((HttpSessionListener) listener);
        }
        if (listener instanceof HttpSessionAttributeListener) {
            httpSessionAttributeListeners.add((HttpSessionAttributeListener) listener);
        }

        if (listener instanceof HttpSessionActivationListener) {
            httpSessionActivationListeners.add((HttpSessionActivationListener) listener);
        }

        if (listener instanceof ServletRequestListener) {
            servletRequestListeners.add((ServletRequestListener) listener);
        }

        if (listener instanceof ServletRequestAttributeListener) {
            servletRequestAttributeListeners.add((ServletRequestAttributeListener) listener);
        }
    }


    private Servlet createServlet(String servletClass) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> clazz = Class.forName(servletClass, true, webAppClassLoader);
        return (Servlet) clazz.newInstance();
    }

    public void addServlet(String servletName, String servletClass) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        addServlet(servletName, servletClass, new HashMap<>(), -1);
    }

    public void addServlet(String servletName, String servletClass, Map<String, String> initParams, int loadOnStartup) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Servlet servlet = createServlet(servletClass);
        ServletWrapper servletWrapper =new ServletWrapper(servletName, servlet, initParams, loadOnStartup);
        this.servletWrappers.add(servletWrapper);
    }

    public void addServletMapping(String servletName, String urlPattern) {
        ServletWrapper servletWrapper = null;
        for (ServletWrapper sw : servletWrappers) {
            if (sw.getServletName().equals(servletName)) {
                servletWrapper = sw;
                break;
            }
        }
        if (servletWrapper == null ) {
            throw new IllegalArgumentException("");
        }
        ServletMapping servletMapping = new ServletMapping(urlPattern, servletWrapper);
        this.servletMappings.add(servletMapping);
    }

    public Filter createFilter(String filterClass) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> clazz = Class.forName(filterClass, true, webAppClassLoader);
        return (Filter) clazz.newInstance();
    }

    public void addFilter(String filterName, String filterClass) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Filter filter = createFilter(filterClass);
        FilterWrapper filterWrapper =new FilterWrapper(filterName, filter);
        this.filterWrappers.add(filterWrapper);
    }

    public void addFilterMapping(String filterName, String urlPattern) {
        FilterWrapper filterWrapper = null;
        for (FilterWrapper fw : filterWrappers) {
            if (fw.getFilterName().equals(filterName)) {
                filterWrapper = fw;
                break;
            }
        }
        if (filterWrapper == null ) {
            throw new IllegalArgumentException("");
        }
        FilterMapping filterMapping = new FilterMapping(urlPattern, filterWrapper);
        this.filterMappings.add(filterMapping);
    }

    public String getContextPath() {
        return contextPath;
    }

    public File getContextBase() {
        return contextBase;
    }

    public void load() {
        logger.info("loading WebApplicationContext:%s", contextPath);

        try {
            webAppDeployHelper.parseWebDescriptor();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (JAXBException e) {
            e.printStackTrace();
        }

        servletContext = new WebApplicationContextFacade(this);

        for(ServletContextListener scl: servletContextListeners) {
            String servletContextName = "";
            ServletContextEvent servletContextEvent = new ServletContextEvent(servletContext);
            scl.contextInitialized(servletContextEvent);
        }

    }

    @Override
    public void recevieConnection(Connection connection) {
        String requestURI = connection.getRequest().getRequestURI();
        if (logger.isDebugEnabled()) {
            logger.debug("recevieConnection requestURI=%s", requestURI);
        }
        String pathInContext = requestURI.substring(contextPath.length());
        for (ServletMapping mapping : servletMappings) {
            String pattern = mapping.getUrlPattern();
            if (pattern.equals(pathInContext)) {
                try {
                    mapping.getServletWrapper().getServlet().service(connection.getRequest(), connection.getResponse());
                } catch (ServletException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        postDispatcheConnection(connection);

    }

    public void postDispatcheConnection(Connection connection) {
        HttpServletResponseImpl response = (HttpServletResponseImpl) connection.getResponse();
        response.completeResponse();
    }

    @Override
    protected void initInternal() throws LifecycleException {
        initClassLoader();
    }

    private void initClassLoader() {
        this.webAppClassLoader = new WebAppClassLoader(new URL[]{});
    }

    @Override
    protected void startInternal() throws LifecycleException {
        setState(LifecycleState.STARTING);
        load();
    }

    @Override
    protected void stopInternal() throws LifecycleException {

    }

    @Override
    protected void destroyInternal() throws LifecycleException {


    }

    public WebApplicationContainer getContainer() {
        return container;
    }

    private void fireServletContextInit() {
        if(CollectionUtils.isEmpty(servletContextListeners)) {
            //ServletContextEvent event = new ServletContextEvent();
            return;
        }
        
    }

    private void fireServletContextDes() {
        if(CollectionUtils.isEmpty(servletContextListeners)) {
            return;
        }
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public void setContextParams(Map<String, String> contextParams) {
        this.contextParams = contextParams;
    }

    public Map<String, String> getContextParams() {
        return contextParams;
    }
}
